package com.lap.mybatis.starter.plugins;

import com.lap.context.starter.handler.UserHandler;
import com.lap.framework.common.entity.Auditable;
import com.lap.framework.common.entity.Tenantable;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;

/**
 * Mybatis Plugin 自定义，实现对更新和插入操作的修改人和更新人字段赋值
 *
 * <p>参考 Mybatis 官网: {@code https://mybatis.org/mybatis-3/zh/configuration.html#plugins}
 *
 * <pre>{@code
 * <plugins>
 *   <plugin interceptor="com.example.mybatis.plugin.AuditFieldInterceptor">
 *     <property name="createdByField" value="createUser"/>
 *     <property name="updatedByField" value="updateUser"/>
 *   </plugin>
 * </plugins>
 * }</pre>
 *
 * @author Shuisheng.Lao(劳水生)
 * @version 0.0.1
 * @see com.lap.mybatis.starter.annotation.MybatisBatch
 */
@Slf4j
@Intercepts({
  @Signature(
      type = Executor.class,
      method = "update",
      args = {MappedStatement.class, Object.class})
})
public class EntityAutoFillInterceptor implements Interceptor {

  /** 最大递归深度，防止循环引用导致栈溢出 */
  private static final int MAX_RECURSION_DEPTH = 30;

  /** 不可变类型集合，直接跳过 */
  private static final Set<Class<?>> IMMUTABLE_TYPES =
      Set.of(
          String.class,
          Byte.class,
          Short.class,
          Integer.class,
          Long.class,
          Float.class,
          Double.class,
          Boolean.class,
          Character.class,
          java.util.Date.class,
          java.time.temporal.Temporal.class // LocalDateTime, LocalDate, etc.
          );

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    Object[] args = invocation.getArgs();
    if (args == null || args.length < 2) {
      return invocation.proceed();
    }

    MappedStatement ms = (MappedStatement) args[0];
    Object parameter = args[1];

    // 只处理 INSERT/UPDATE
    if (!isAuditOperation(ms)) {
      return invocation.proceed();
    }

    try {
      if (parameter != null) {
        processArgument(parameter, ms.getSqlCommandType(), new IdentityHashMap<>(), 0);
      }
    } catch (Exception e) {
      log.warn("Failed to set audit/tenant fields, continue execution: {}", e.getMessage());
      if (log.isDebugEnabled()) {
        log.debug("Audit/Tenant processing error details: ", e);
      }
    }

    return invocation.proceed();
  }

  /** 是否需要填充审计字段 */
  private boolean isAuditOperation(MappedStatement ms) {
    SqlCommandType type = ms.getSqlCommandType();
    return type == SqlCommandType.INSERT || type == SqlCommandType.UPDATE;
  }

  /** 递归填充逻辑 */
  private void processArgument(
      Object arg, SqlCommandType sqlType, Map<Object, Boolean> processed, int depth) {
    if (arg == null || processed.containsKey(arg) || depth > MAX_RECURSION_DEPTH) {
      return;
    }
    processed.put(arg, true);

    // 跳过基础类型 / 不可变对象
    if (isImmutable(arg)) {
      return;
    }

    if (arg instanceof Collection<?> collection) {
      // Collection 内对象递归填充，基础类型跳过
      for (Object item : collection) {
        if (!isImmutable(item)) {
          processArgument(item, sqlType, processed, depth + 1);
        }
      }
    } else if (arg instanceof Map<?, ?> map) {
      // Map key 不处理，value 递归
      for (Object value : map.values()) {
        processArgument(value, sqlType, processed, depth + 1);
      }
    } else if (arg.getClass().isArray()) {
      if (arg instanceof Object[] objectArray) {
        for (Object item : objectArray) {
          if (!isImmutable(item)) {
            processArgument(item, sqlType, processed, depth + 1);
          }
        }
      }
    } else {
      // === 核心接口填充 ===
      if (arg instanceof Auditable auditable) {
        setAudit(auditable, sqlType);
      }
      if (arg instanceof Tenantable tenantable) {
        setTenant(tenantable);
      }
    }
  }

  /** 判断对象是否为基础类型或不可变类型 */
  private boolean isImmutable(Object obj) {
    if (obj == null) return true;
    Class<?> clazz = obj.getClass();
    if (clazz.isPrimitive()) return true;
    for (Class<?> immutable : IMMUTABLE_TYPES) {
      if (immutable.isAssignableFrom(clazz)) {
        return true;
      }
    }
    return false;
  }

  /** 填充审计字段 */
  private void setAudit(Auditable audit, SqlCommandType type) {
    String username = UserHandler.getFullName();
    if (type == SqlCommandType.INSERT) {
      audit.setCreatedBy(username);
      audit.setUpdatedBy(username);
    } else if (type == SqlCommandType.UPDATE) {
      audit.setUpdatedBy(username); // UPDATE 一定覆盖
    }
  }

  /** 填充租户字段 */
  private void setTenant(Tenantable tenantable) {
    String tenantCode = UserHandler.getTenantCode();
    if (tenantCode != null) {
      tenantable.setTenantCode(tenantCode);
    }
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
    log.info("Initializing plugin with properties: {}", properties);
  }
}
